package org.runecore.rs2.model;

import java.nio.channels.SelectionKey;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.Executor;
import java.util.logging.Logger;

import org.runecore.Launch;
import org.runecore.rs2.EntityList;
import org.runecore.rs2.model.player.GameSession;
import org.runecore.rs2.model.player.Player;
import org.runecore.rs2.net.codec.*;

/**
 * RuneCore
 * Sep 4, 2011
 * @author Harry Andreas<Harry@RuneCore.org>
 */
public class World {
	
	/**
	 * The World instance
	 */
	private static World world;
	
	/**
	 * The <code>Logger</code> instance 
	 */
	private final Logger logger = Launch.getEnv().getLogger();
	
	/**
	 * The <code>EntityList</code> for <code>Player</code>'s
	 */
	private EntityList<Player> worldPlayers;
	
	/**
	 * A <code>Map</code> holding data for <code>GameSession</code>'s
	 */
	private Map<SelectionKey, GameSession> sessionMap;
	
	/**
	 * The <code>AbstractGameRevision</code> that has been selected
	 */
	private AbstractGameRevision selectedRevision;
	
	/**
	 * Constructor
	 * @throws Exception 
	 */
	private World() throws Exception {
		setWorldPlayers(new EntityList<Player>(Integer.parseInt(((String)Launch.getEnv().getAttribute("max-players")))));
		setSessionMap(new HashMap<SelectionKey, GameSession>());
		int version = (Integer)Launch.getEnv().getAttribute("gametype");
		for(AbstractGameRevision rev : Launch.getEnv().getLoadedRevisions()) {
			if(rev.getRevision() == version) {
				setSelectedRevision(rev);
				break;
			}
		}
		if(getSelectedRevision() == null) {
			throw new Exception("No Game Revision Selected!");
		}
		getLogger().info("Server enabled for revision #"+version);
	}
	
	/**
	 * Register's a new <code>Entity</code>
	 * @param e The <code>Entity</code> instance
	 * @return If it registered the <code>Entity</code>
	 */
	private boolean register(Entity e) {
		if(e instanceof Player) {
			return getWorldPlayers().add((Player)e);
		}
		return false;
	}
	
	/**
	 * Handle's the <code>Player</code> event's
	 * @throws Exception
	 */
	public void handlePlayerProcess() throws Exception {
		/*
		if(getWorldPlayers().size() < 1) {
			return;
		}
		*/
		/**
		 * Process tick's for each player
		 */
		for(Player player : getWorldPlayers()) {
			player.tick();
		}
		
		/**
		 * Now execute the player updating
		 * in a thread pool
		 */
		final CountDownLatch latch = new CountDownLatch(getWorldPlayers().size());
		for(final Player player : getWorldPlayers()) {
			 Executor pool =  Launch.getEnv().getIOCore();
			 pool.execute(new Runnable() {
				@Override
				public void run() {
					try {
						synchronized(player) {
							player.update();
						}
					} catch(Exception e) {
						e.printStackTrace();
					} finally {
						latch.countDown();
					}
				} 
			 });
		}
		
		/**
		 * Await for the tasks to finish..
		 */
		latch.await();
		
		/**
		 * Finally reset everybody's mask's
		 */
		for(Player player : getWorldPlayers()) {
			player.getEntityMask().reset();
		}
		
	}
	
	/**
	 * Registers a new player
	 * @param p The <code>Player</code> to register
	 */
	public void register(Player p) {
		if(register((Entity) p)) {
			getLogger().info("Registered player "+p.getUsername()+" at index "+p.getIndex()+".");
		}
	}
	
	/**
	 * Check's to see if the <code>Player</code> is online
	 * @param name The name to check
	 * @return The status of the player
	 */
	public boolean userOnline(String name) {
		for(Player p : getWorldPlayers()) {
			if(p != null) {
				if(p.getUsername().equalsIgnoreCase(name)) {
					return true;
				}
			}
		}
		return false;
	}
	
	/**
	 * Get's the world instance
	 * @return
	 */
	public static World getSingleton() {
		if(world == null) {
			try {
				world = new World();
			} catch (Exception e) {
				e.printStackTrace();
				System.exit(-1);
			}
		}
		return world;
	}

	/**
	 * @param worldPlayers the worldPlayers to set
	 */
	public void setWorldPlayers(EntityList<Player> worldPlayers) {
		this.worldPlayers = worldPlayers;
	}

	/**
	 * @return the worldPlayers
	 */
	public EntityList<Player> getWorldPlayers() {
		return worldPlayers;
	}

	/**
	 * @param sessionMap the sessionMap to set
	 */
	public void setSessionMap(Map<SelectionKey, GameSession> sessionMap) {
		this.sessionMap = sessionMap;
	}

	/**
	 * @return the sessionMap
	 */
	public Map<SelectionKey, GameSession> getSessionMap() {
		return sessionMap;
	}


	/**
	 * @return the logger
	 */
	public Logger getLogger() {
		return logger;
	}

	/**
	 * @return the selectedRevision
	 */
	public AbstractGameRevision getSelectedRevision() {
		return selectedRevision;
	}

	/**
	 * @param selectedRevision the selectedRevision to set
	 */
	public void setSelectedRevision(AbstractGameRevision selectedRevision) {
		this.selectedRevision = selectedRevision;
	}

}